package org.little.context.annotation;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.little.beans.BeanDefinition;
import org.little.beans.factory.BeanDefinitionStoreException;
import org.little.beans.factory.support.BeanDefinitionRegistry;
import org.little.beans.factory.support.BeanNameGenerotor;
import org.little.core.io.Resource;
import org.little.core.io.support.PackageResourceLoader;
import org.little.core.type.MetadataReader;
import org.little.core.type.classreading.SimpleMetadataReader;
import org.little.stereotype.Component;
import org.little.util.StringUtils;

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * @author Tinyspot
 * @date 2022-05-23 22:26
 */
public class ClassPathBeanDefinitionScanner {

    protected final Log logger = LogFactory.getLog(this.getClass());

    private final BeanDefinitionRegistry registry;

    private PackageResourceLoader resourceLoader = new PackageResourceLoader();

    private BeanNameGenerotor beanNameGenerotor = new AnnotationBeanNameGenerator();

    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        this.registry = registry;
    }

    public Set<BeanDefinition> doScan(String packageToScan) {
        String[] basePackages = StringUtils.tokenizeToStringArray(packageToScan, ",");

        Set<BeanDefinition> beanDefinitions = new LinkedHashSet<>();
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                beanDefinitions.add(candidate);
                registry.registerBeanDefinition(candidate.getId(), candidate);
            }
        }
        return beanDefinitions;
    }

    public Set<BeanDefinition> findCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        try {
            Resource[] resources = this.resourceLoader.getResources(basePackage);
            for (Resource resource : resources) {
                try {
                    MetadataReader metadataReader = new SimpleMetadataReader(resource);
                    if (metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName())) {
                        ScannedGenericBeanDefinition scanDefinition = new ScannedGenericBeanDefinition(metadataReader.getAnnotationMetadata());
                        String beanName = this.beanNameGenerotor.generateBeanName(scanDefinition, this.registry);
                        scanDefinition.setId(beanName);
                        candidates.add(scanDefinition);
                    }
                } catch (Throwable throwable) {
                    throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, throwable);
                }
            }
        } catch (IOException e) {
            throw new BeanDefinitionStoreException("I/O failure during classpath scanning", e);
        }
        return candidates;
    }
}
